/*
 * Use or Copy is free, but retain author declaration.
 */
package pyip.lib.mixtool.simplehttpclient;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author yepeng
 * @date 2014年12月22日
 */
public class HttpClient {

    private static final Logger logger = LoggerFactory.getLogger(HttpClient.class);

    /** 内部不要直接读取config, 而是通过#getConfig()来获得 */
    private RequestConfig config;
    private static final RequestConfig DEFAULT_CONFIG;
    static {
        DEFAULT_CONFIG = new RequestConfig();
        DEFAULT_CONFIG.setConnectTimeout(10000);
        DEFAULT_CONFIG.setReadTimeout(15000);
        DEFAULT_CONFIG.setRequestCharset("utf-8");
        DEFAULT_CONFIG.setResponseCharset("utf-8");
    }

    /**
     * @return 全局配置, 可通过修改返回的对象改变全局配置
     */
    public RequestConfig getConfig() {
        if (config == null) {
            return config = DEFAULT_CONFIG.clone();
        } else {
            if (StringUtils.isBlank(config.getRequestCharset())) {
                config.setRequestCharset(DEFAULT_CONFIG.getRequestCharset());
            }
            if (StringUtils.isBlank(config.getResponseCharset())) {
                config.setResponseCharset(DEFAULT_CONFIG.getResponseCharset());
            }
            if (config.getConnectTimeout() <= 0) {
                config.setConnectTimeout(DEFAULT_CONFIG.getConnectTimeout());
            }
            if (config.getReadTimeout() <= 0) {
                config.setReadTimeout(DEFAULT_CONFIG.getReadTimeout());
            }
            return config;
        }
    }

    public void setConfig(RequestConfig config) {
        this.config = config;
    }

    /** 不填充ResponseCharset */
    private void fillGlobalConfig(Request request) {
        RequestConfig config = request.getConfig();
        if (config == null) {
            config = new RequestConfig();
            request.setConfig(config);
        }

        RequestConfig globalConfig = getConfig();
        if (StringUtils.isBlank(config.getRequestCharset())) {
            config.setRequestCharset(globalConfig.getRequestCharset());
        }
        if (config.getConnectTimeout() <= 0) {
            config.setConnectTimeout(globalConfig.getConnectTimeout());
        }
        if (config.getReadTimeout() <= 0) {
            config.setReadTimeout(globalConfig.getReadTimeout());
        }
    }

    private static String processParams(Request request) throws UnsupportedEncodingException {
        if (request == null || request.getParams() == null) {
            return null;
        }

        String charset = request.getConfig().getRequestCharset();
        String str = "";
        for (Entry<String, String[]> entry : request.getParams().entrySet()) {
            if (StringUtils.isNotBlank(entry.getKey())) {
                if (entry.getValue() == null || entry.getValue().length == 0) {
                    str += "&" + encode(entry.getKey(), charset);
                } else if (entry.getValue().length == 1) {
                    str += "&" + encode(entry.getKey(), charset);
                    if (StringUtils.isNotBlank(entry.getValue()[0])) {
                        str += "=" + encode(entry.getValue()[0], charset);
                    }
                } else {
                    boolean hasAppendEmptyParam = false;
                    for (String v : entry.getValue()) {
                        if (StringUtils.isNotBlank(v) || !hasAppendEmptyParam) {
                            hasAppendEmptyParam = true;
                            str += "&" + encode(entry.getKey(), charset);
                        }
                        if (StringUtils.isNotBlank(v)) {
                            str += "=" + encode(v, charset);
                        }
                    }
                }
            }
        }

        if (str.startsWith("&")) {
            str = str.substring(1);
        }

        return str;
    }

    private static String encode(String value, String charset) throws UnsupportedEncodingException {
        return StringUtils.isNotBlank(charset) ? URLEncoder.encode(value, charset) : value;
    }

    public String execute(Request request) throws IOException {
        if (request == null) {
            throw new NullPointerException("参数request不能为空");
        }

        fillGlobalConfig(request);
        if (logger.isDebugEnabled()) {
            logger.debug("request config:");
            logger.debug("\tconnectTimeout=" + request.getConfig().getConnectTimeout());
            logger.debug("\treadTimeout=" + request.getConfig().getReadTimeout());
            logger.debug("\trequestCharset=" + request.getConfig().getRequestCharset());
            logger.debug("\tresponseCharset=" + request.getConfig().getResponseCharset());
        }

        StringBuilder resultContent = new StringBuilder();

        String url = request.getUrl();
        String paramsStr = processParams(request);
        if (request.getMethod().equals(Request.METHOD_NAME_GET) && StringUtils.isNotBlank(paramsStr)) {
            if (url.endsWith("?")) {
                url = url + paramsStr;
            } else if (url.contains("?")) {
                if (url.endsWith("&")) {
                    url = url.substring(0, url.length() - 1);
                }
                url = url + "&" + paramsStr;
            } else {
                url = url + "?" + paramsStr;
            }
        }
        URL _url = new URL(url);

        PrintWriter out = null;
        InputStream in = null;
        HttpURLConnection connection = null;
        try {
            connection = (HttpURLConnection) _url.openConnection();
            // HttpURLConnection connection1 = (HttpURLConnection) _url.openConnection();
            connection.setConnectTimeout(request.getConfig().getConnectTimeout());
            connection.setReadTimeout(request.getConfig().getReadTimeout());

            // 设置请求头
            if (request.getMethod().equals(Request.METHOD_NAME_POST)) {
                connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
                // 不使用缓存
                connection.setUseCaches(false);
            }
            Map<String, String> requestHeaders = request.getHeaders();
            if (requestHeaders != null) {
                for (Entry<String, String> entry : requestHeaders.entrySet()) {
                    connection.setRequestProperty(entry.getKey(), entry.getValue());
                }
            }

            if (logger.isDebugEnabled()) {
                logger.debug("request method: " + request.getMethod());
                logger.debug("request url: " + request.getUrl());
                logger.debug("request parameters:");
                if (StringUtils.isNotBlank(paramsStr)) {
                    String[] params = paramsStr.split("&");
                    for (String param : params) {
                        logger.debug("\t" + param);
                    }
                }
                logger.debug("request headers:");
                for (Entry<String, List<String>> entry : connection.getRequestProperties().entrySet()) {
                    logger.debug("\t" + entry.getKey() + "=" + entry.getValue());
                }
            }

            // 设置POST请求方式
            if (request.getMethod().equals(Request.METHOD_NAME_POST)) {
                connection.setDoOutput(true);
                connection.setDoInput(true);
                // 发送请求参数
                if (StringUtils.isNotBlank(paramsStr)) {
                    // connection.getOutputStream()会触发连接建立
                    out = new PrintWriter(connection.getOutputStream());
                    out.print(paramsStr);
                    out.flush();
                }
            }

            // 建立连接
            // HttpURLConnection的connect()函数，实际上只是建立了一个与服务器的tcp连接，并没有实际发送http请求。
            // 无论是post还是get，http请求实际上直到HttpURLConnection的getInputStream()这个函数里面才正式发送出去。
            connection.connect();

            // 获取所有响应头字段
            Map<String, List<String>> responseHeaders = connection.getHeaderFields();
            if ((logger.isDebugEnabled())) {
                if (responseHeaders.isEmpty()) {
                    logger.debug("response: error");
                } else {
                    logger.debug("response: " + responseHeaders.get(null));
                    logger.debug("response headers:");
                    for (String key : responseHeaders.keySet()) {
                        if (key != null) {
                            logger.debug("\t" + key + "=" + responseHeaders.get(key));
                        }
                    }
                }
            }

            // 优先使用request#getConfig()#getResponseCharset(), 其次response header contentType charset, 最后spider#getConfig()#getResponseCharset()
            String charset = getConfig().getResponseCharset();
            if (StringUtils.isNotBlank(request.getConfig().getResponseCharset())) {
                charset = request.getConfig().getResponseCharset();
            } else {
                List<String> contentTypeHeader = responseHeaders.get("Content-Type");
                if (contentTypeHeader != null && !contentTypeHeader.isEmpty()) {
                    String contentType = contentTypeHeader.get(0).toLowerCase();
                    String key = "charset=";
                    if (contentType.indexOf(key) != -1) {
                        String tempCharset = contentType.substring(contentType.indexOf(key) + key.length(), contentType.length());
                        if (StringUtils.isNotBlank(tempCharset)) {
                            charset = tempCharset;
                        }
                    }
                }
            }

            if ((logger.isDebugEnabled())) {
                logger.debug("decode response body with charset: " + charset);
            }
            
            // 成功则读取结果
            if(connection.getResponseCode() == 200) {
                in = connection.getInputStream();
                BufferedReader br = new BufferedReader(new InputStreamReader(in, charset));
                String line = null;
                while ((line = br.readLine()) != null) {
                    resultContent.append(line);
                }
            }
        } finally {
            if (out != null) {
                out.close();
            }
            if (in != null) {
                in.close();
            }
            if (connection != null) {
                connection.disconnect();
            }
        }

        String result = resultContent.toString();
        if ((logger.isDebugEnabled())) {
            logger.debug("result:\n" + result);
        }

        return result;
    }

}
